/* Copyright (C) 2014-2016 Dan Chapman <dpniel@ubuntu.com>

   This file is part of Dekko email client for Ubuntu Devices/

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of
   the License or (at your option) version 3

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef MESSAGEMODEL_H
#define MESSAGEMODEL_H
#include <QtQml/QtQml>
#include <QDateTime>
#include <QDebug>
#include <QPersistentModelIndex>
#include <QVariant>
#include <QStack>
#include <QUrl>
#include <QUrlQuery>
#include <QNetworkAccessManager>
#include "Imap/Model/OneMessageModel.h"
#include "Imap/Model/SubtreeModel.h"
#include "Imap/Model/Model.h"
#include "3rdParty/trojita/Imap/Network/MsgPartNetAccessManager.h"
#include "Imap/Parser/MailAddress.h"
#include "app/Components/QmlObjectListModel.h"
#include "app/Utils/Helpers.h"
#include "app/Utils/AttachmentDownloader.h"
#include "app/Accounts/MultipleAccountSenderIdentityModel.h"

namespace Dekko
{
namespace Models
{
class InlinePart;
class MessageAttachment;
class MessageContact;
class MessageModel : public QObject
{
    Q_OBJECT

    Q_PROPERTY(QObject *attachments READ attachments NOTIFY attachmentsChanged )
    Q_PROPERTY(QObject * imapModel READ imapModel WRITE setImapModel)
    // Message envelope properties
    Q_PROPERTY(QString subject READ subject NOTIFY envelopeChanged)
    Q_PROPERTY(QDateTime date READ date NOTIFY envelopeChanged)
    Q_PROPERTY(QObject *from READ from NOTIFY envelopeChanged)
    Q_PROPERTY(QObject *to READ to NOTIFY envelopeChanged)
    Q_PROPERTY(QObject *cc READ cc NOTIFY envelopeChanged)
    Q_PROPERTY(QObject *bcc READ bcc NOTIFY envelopeChanged)
    Q_PROPERTY(QString toRecipientsString READ toRecipientsString NOTIFY envelopeChanged)
    //    Q_PROPERTY(QVariantList replyTo READ replyTo NOTIFY envelopeChanged)
    //    Q_PROPERTY(QByteArray inReplyTo READ inReplyTo NOTIFY envelopeChanged)
    Q_PROPERTY(QByteArray messageId READ messageId NOTIFY envelopeChanged)
    Q_PROPERTY(bool hasValidIndex READ hasValidIndex NOTIFY messageChanged)
    Q_PROPERTY(QModelIndex messageIndex READ messageIndex NOTIFY envelopeChanged)
    Q_PROPERTY(bool isListPost READ isListPost NOTIFY envelopeChanged)
    Q_PROPERTY(QList<QByteArray> references READ references NOTIFY envelopeChanged)
    // Message part properties
    Q_PROPERTY(QModelIndex messageFirstChildIndex READ messageFirstChildIndex NOTIFY envelopeChanged)
    Q_PROPERTY(int uid READ uid NOTIFY envelopeChanged)
    Q_PROPERTY(QString mailbox READ mailbox WRITE setMailbox NOTIFY mailboxChanged)
    Q_PROPERTY(QModelIndex mainTextPartIndex READ mainTextPartIndex NOTIFY mainTextPartIndexChanged)
    Q_PROPERTY(QUrl mainPartUrl READ mainPartUrl NOTIFY mainPartUrlChanged)
    Q_PROPERTY(bool mainPartIsHtml READ mainPartIsHtml NOTIFY mainPartUrlChanged)
    // Message flags
    Q_PROPERTY(bool isMarkedDeleted READ isMarkedDeleted NOTIFY flagsChanged)
    Q_PROPERTY(bool isMarkedRead READ isMarkedRead  NOTIFY flagsChanged)
    Q_PROPERTY(bool isMarkedForwarded READ isMarkedForwarded NOTIFY flagsChanged)
    Q_PROPERTY(bool isMarkedReplied READ isMarkedReplied NOTIFY flagsChanged)
    Q_PROPERTY(bool isMarkedRecent READ isMarkedRecent NOTIFY flagsChanged)
    Q_PROPERTY(bool isMarkedFlagged READ isMarkedFlagged NOTIFY flagsChanged)
    Q_PROPERTY(QStringList accountIds WRITE setAccountIds)

    QML_WRITABLE_PROPERTY(bool, preferPlainText)
    QML_WRITABLE_PROPERTY(QString, accountId)


public:
    explicit MessageModel(QObject * parent = 0);
    MessageModel(QObject *parent, Imap::Mailbox::Model *model);

    void initialize();

    QObject *attachments() const;

    QString subject() const;
    QDateTime date() const;
    QObject *from() const;
    QObject *to() const;
    QObject *cc() const;
    QObject *bcc() const;
    QString toRecipientsString();

    bool hasValidIndex() const;
    bool isListPost() const;
    QList<QByteArray> references() const;
    QByteArray messageId() const;
    QModelIndex messageFirstChildIndex() const;
    QModelIndex messageIndex() const;

    QObject * imapModel();
    void setImapModel(QObject * imapModel);
    void setModel(Imap::Mailbox::Model *model);
    uint uid();
    QString mailbox();
    void setMailbox(QString mailbox);
    QModelIndex mainTextPartIndex();

    bool isMarkedDeleted() const;
    bool isMarkedRead() const;
    bool isMarkedForwarded() const;
    bool isMarkedReplied() const;
    bool isMarkedRecent() const;
    bool isMarkedFlagged() const;

    Q_INVOKABLE void openMessage();
    Q_INVOKABLE void markMessageDeleted();
    Q_INVOKABLE void markMessageRead(bool marked);
    Q_INVOKABLE void markMessageFlagged();
    Q_INVOKABLE void setMessageModelIndex(QModelIndex index);
    Q_INVOKABLE QModelIndex getModelIndexFromPartId(QString partId);
    Q_INVOKABLE QList<QByteArray> replyReferences() const;

    QUrl mainPartUrl();
    bool mainPartIsHtml() const;

    void setAccountIds(const QStringList &ids);

signals:
    void mailboxChanged();
    void mainTextPartIndexChanged();
    void messageChanged();
    void envelopeChanged();
    void flagsChanged();
    void mainPartUrlChanged();
    void attachmentsChanged();
    void partNowAvailable(QModelIndex &part);

public slots:
    void setMessage(const QString &mboxName, const uint uid);
    void setMessage(const QModelIndex &message);
    void messageClosed();
    void fetchPart(const QModelIndex &msgIndex, const QModelIndex &part);

private slots:
    void handleModelDataChanged(const QModelIndex &topLeft, const QModelIndex &btmRight);
    void handlePartFetched();

private:
    void walkMessage(const QModelIndex &partIndex, const int recursionDepth, const bool isPreferredIndex);
    void walkMultipartAlternative(const QModelIndex &partIndex, const int recursionDepth);
    void walkMultipartSigned(const QModelIndex &partIndex, const int recursionDepth);
    void walkGenericMultiPart(const QModelIndex &partIndex, const int recursionDepth);
    void initContactModels();

    // members
    QQmlObjectListModel<MessageContact> *m_from;
    QQmlObjectListModel<MessageContact> *m_to;
    QQmlObjectListModel<MessageContact> *m_cc;
    QQmlObjectListModel<MessageContact> *m_bcc;
    QQmlObjectListModel<MessageAttachment> *m_attachments;
    Imap::Mailbox::Model *m_imapModel;
    int m_uid;
    QString m_mailbox;
    QPersistentModelIndex m_message;
    QModelIndex m_mainPartIndex;
    QModelIndex m_fetchPartIndex;
    QPointer<Imap::Mailbox::SubtreeModelOfModel> m_subtree;
    QUrl m_mainPartUrl;

    QStack<InlinePart *> m_inlineParts;

    Imap::Network::MsgPartNetAccessManager *m_mpnam;
    QNetworkReply *m_reply;
    Accounts::MultipleAccountSenderIdentityModel *m_identities;
};

class MessageContact : public QObject {
    Q_OBJECT
    Q_PROPERTY(QString initials READ initials NOTIFY contactChanged)
    Q_PROPERTY(QString name READ name NOTIFY contactChanged)
    Q_PROPERTY(QString fullDisplayName READ displayName NOTIFY contactChanged)
    Q_PROPERTY(QString email READ email NOTIFY contactChanged)
    Q_ENUMS(Type)
    Imap::Message::MailAddress m_mailAddress;
public:
    enum Type {FROM, TO, CC, BCC};
    MessageContact(QObject *parent, const Imap::Message::MailAddress &mailAddress);
    QString initials();
    QString name() const;
    QString displayName() const;
    QString email() const;
signals:
    void contactChanged();
};

class InlinePart : QObject {
    Q_OBJECT
public:
    enum CType {PLAIN, HTML};
    InlinePart(QObject *parent, const QString &path, const QString &accId, const QString &mbox, const QString &id, const CType type);
    CType contentType;
    QString pathToPart;
    QString accountId;
    QString uid;
    QString mailbox;
    bool isPlaintText();
    bool isHtml();
    QUrl pathToPartUrl();
};

class MessageAttachment : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QModelIndex partIndex READ partIndex NOTIFY partIndexChanged)
    Q_PROPERTY(QString partId READ partId NOTIFY partIndexChanged)
    Q_PROPERTY(QString mimeType READ mimeType NOTIFY partIndexChanged)
    Q_PROPERTY(QString iconName READ iconName NOTIFY partIndexChanged)
    Q_PROPERTY(QString fileName READ fileName NOTIFY partIndexChanged)
    Q_PROPERTY(QString size READ size NOTIFY partIndexChanged)
    Q_PROPERTY(uint downloadProgress READ downloadProgress NOTIFY downloadProgressChanged)
    Q_PROPERTY(QString errorString READ errorString NOTIFY error)
    // partUrlChanged will get emitted on part download success
    Q_PROPERTY(QString partUrl READ partUrl NOTIFY partUrlChanged)
    Q_PROPERTY(bool fetchInProgress READ fetchInProgress NOTIFY fetchInProgressChanged)

public:
    explicit MessageAttachment(QObject *parent = 0):
        QObject(parent), m_downloader(0), m_downloadProgress(0),
        m_fetchInProgress(false)
    {
    }

    QString partId() const;
    QString mimeType() const;
    QString iconName() const;
    QString fileName() const;
    QString size() const;
    QModelIndex partIndex() const;

    void setPartIndex(const QModelIndex &index);

    uint downloadProgress() const;

    Q_INVOKABLE void fetch();
    Q_INVOKABLE void cleanup() const;

    QString errorString() const;

    QString partUrl() const;

    bool fetchInProgress() const;

signals:
    void partIndexChanged();
    void downloadProgressChanged(uint downloadProgress);
    void error(QString errorString);

    void partUrlChanged(QString partUrl);

    void fetchInProgressChanged(bool fetchInProgress);

private slots:
    void handleDownloadProgressChanged(qint64 downloaded, qint64 total);

private:
    QString m_partId;
    QString m_mimeType;
    QString m_iconName;
    QString m_fileName;
    QString m_size;
    QModelIndex m_partIndex;
    Dekko::Utils::AttachmentDownloader *m_downloader;
    uint m_downloadProgress;
    QString m_errorString;
    QString m_partUrl;
    bool m_fetchInProgress;
};
}
}


#endif
